/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.statemachine.helper;

import java.util.Iterator;

import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Region;
import org.eclipse.uml2.uml.State;
import org.eclipse.uml2.uml.UMLFactory;
import org.eclipse.uml2.uml.Vertex;

import com.cea.papyrus.core.helper.NodeHelper;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagram.statemachine.Activator;
import com.cea.papyrus.diagram.statemachine.command.AddRegionNodeCommand;
import com.cea.papyrus.diagram.statemachine.command.ResizeRegionNodeCommand;
import com.cea.papyrus.diagram.statemachine.helper.NodeRegionHelper.Zone;
import com.cea.papyrus.diagram.statemachine.preference.StateMachineDiagramPreferenceConstants;
import com.cea.papyrus.diagraminterchange2.di2.Di2Factory;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Property;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * Manages state.
 * 
 * @author David Servat
 * @since 29 jan. 07
 */
public class NodeStateHelper extends NodeHelper{

	public final static String STATE_DISPLAY = "StateDisplay";
	
	public final static String STATE_NOT_DISPLAY = "StateNotDisplay";
	
	public final static String STATE_DISPLAY_ENTRY = "StateDisplayEntry";
	
	public final static String STATE_DISPLAY_DO_ACTIVITY = "StateDisplayDoActivity";

	public final static String STATE_DISPLAY_EXIT = "StateDisplayExit";

	public final static String STATE_DISPLAY_DEFERRABLE_TRIGGERS = "StateDisplayDeferrableTriggers";

	/**
	 * The channel.
	 */
	private int channel = 501;

	/**
	 * 
	 * 
	 * @param umlChild 
	 * @param size 
	 * @param location 
	 * @param parent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#createGraphElement(org.eclipse.uml2.uml.Element, com.cea.papyrus.diagraminterchange2.di2.GraphElement, org.eclipse.draw2d.geometry.Point, org.eclipse.draw2d.geometry.Dimension)
	 */
	@Override
	public GraphNode createGraphElement(Element umlChild, GraphElement parent, Point location, Dimension size){
		Debug.debug(this,"Call GraphNode NodeStateHelper.createGraphElement(Element umlChild, GraphElement parent, Point location, Dimension size)", channel); //$NON-NLS-1$

		int defaultY = 20;
		int defaultSize = 100;
		
		if((size == null) || size.equals(getGraphNodeSize()))
			//provide a default size for the state machine node
			size = new Dimension(defaultSize, defaultSize);
		
		//caution: must pass a null argument for umlParent to the super class method
		//otherwise the graph node is directly added!!
		GraphNode graphNode = super.createGraphElement(umlChild, null, location, size);

		//set display properties
		Property property = graphNode.getProperty(STATE_DISPLAY_DEFERRABLE_TRIGGERS);
		if(property == null){
			// Create a property for this graph node to hold display property
			property=Di2Factory.eINSTANCE.createProperty();
			property.setKey(STATE_DISPLAY_DEFERRABLE_TRIGGERS);
			if((((State)umlChild).getDeferrableTriggers() != null) && !((State)umlChild).getDeferrableTriggers().isEmpty())
				property.setValue(STATE_DISPLAY);		
			else
				property.setValue(STATE_NOT_DISPLAY);		
			graphNode.getProperty().add(property);
		} 
		else{
			if((((State)umlChild).getDeferrableTriggers() != null) && !((State)umlChild).getDeferrableTriggers().isEmpty())
				property.setValue(STATE_DISPLAY);		
			else
				property.setValue(STATE_NOT_DISPLAY);	
		}
		
		property = graphNode.getProperty(STATE_DISPLAY_DO_ACTIVITY);
		if(property == null){
			// Create a property for this graph node to hold display property
			property=Di2Factory.eINSTANCE.createProperty();
			property.setKey(STATE_DISPLAY_DO_ACTIVITY);
			if(((State)umlChild).getDoActivity() != null)
				property.setValue(STATE_DISPLAY);
			else
				property.setValue(STATE_NOT_DISPLAY);		
			graphNode.getProperty().add(property);
		} 
		else{
			if(((State)umlChild).getDoActivity() != null)
				property.setValue(STATE_DISPLAY);
			else
				property.setValue(STATE_NOT_DISPLAY);	
		}
		
		property = graphNode.getProperty(STATE_DISPLAY_ENTRY);
		if(property == null){
			// Create a property for this graph node to hold display property
			property=Di2Factory.eINSTANCE.createProperty();
			property.setKey(STATE_DISPLAY_ENTRY);
			if(((State)umlChild).getEntry() != null)
				property.setValue(STATE_DISPLAY);		
			else
				property.setValue(STATE_NOT_DISPLAY);		
			graphNode.getProperty().add(property);
		} 
		else{
			if(((State)umlChild).getEntry() != null)
				property.setValue(STATE_DISPLAY);		
			else
				property.setValue(STATE_NOT_DISPLAY);	
		}
		
		property = graphNode.getProperty(STATE_DISPLAY_EXIT);
		if(property == null){
			// Create a property for this graph node to hold display property
			property=Di2Factory.eINSTANCE.createProperty();
			property.setKey(STATE_DISPLAY_EXIT);
			if(((State)umlChild).getExit() != null)
				property.setValue(STATE_DISPLAY);		
			else
				property.setValue(STATE_NOT_DISPLAY);		
			graphNode.getProperty().add(property);
		} 
		else{
			if(((State)umlChild).getExit() != null)
				property.setValue(STATE_DISPLAY);		
			else
				property.setValue(STATE_NOT_DISPLAY);	
		}
		
		if((((State)umlChild).getRegions() != null) && !((State)umlChild).getRegions().isEmpty()){
			//retrieve the first region
			Region aRegion = (Region)((State)umlChild).getRegions().get(0);
			
			//create a node for this default region with correct position and size
			GraphNode aRegionNode = super.createGraphElement(aRegion, graphNode, new Point(0, defaultY), new Dimension(size.width, size.height-defaultY));

			//sets its semantic parent
			aRegionNode.setSemanticParent(graphNode);
			
			//assign default values for zone and dropLocation properties
			Property prop = Di2Factory.eINSTANCE.createProperty();
			prop.setKey("zone");
			prop.setValue(Zone.NONE);
			aRegionNode.getProperty().add(prop);

			prop = Di2Factory.eINSTANCE.createProperty();
			prop.setKey("dropLocation");
			prop.setValue(Zone.NONE);
			aRegionNode.getProperty().add(prop);
			
			GraphNode labelNode = new NodeHelper().createGraphElement(null, null, new Point(0, 0), null);
			labelNode.setSemanticParent(aRegionNode);
		    labelNode.setContainer(aRegionNode);
		    labelNode.setIsVisible(false);

		    //iterate on the list of regions
			Iterator regions = ((State)umlChild).getRegions().iterator();
			//calculate an equal size for the regions
			int nRegions = ((State)umlChild).getRegions().size();
			int equalSize = (int)Math.floor((defaultSize-defaultY)/nRegions);
			
			//pass the first default region
			regions.next();
			GraphNode previousRegionNode = aRegionNode;
			while(regions.hasNext()){
				//create a new Region node
				//Note: the null argument for the parent node; this region will not be attached directly to the state node
				//but to a splitter node when executing the AddREgionNodeCommand
				GraphNode newRegionNode = super.createGraphElement((Region)regions.next(), null, null, new Dimension(size.width, size.height-defaultY));
				//assign default values for zone and dropLocation properties; a RIGHT drop location is set so that regions will be created in horizontal row
				prop = Di2Factory.eINSTANCE.createProperty();
				prop.setKey("zone");
				prop.setValue(Zone.NONE);
				newRegionNode.getProperty().add(prop);

				prop = Di2Factory.eINSTANCE.createProperty();
				prop.setKey("dropLocation");
				prop.setValue(Zone.BOTTOM);
				newRegionNode.getProperty().add(prop);
				
				labelNode = new NodeHelper().createGraphElement(null, null, new Point(0, 0), null);
				labelNode.setSemanticParent(newRegionNode);
			    labelNode.setContainer(newRegionNode);
			    labelNode.setIsVisible(false);

				//Note: do not set the semantic link now, it will be done later in the AddRegionCommand
				//this serves to distinguish between a region created and added (no semantic link set) and a region simply dragged and dropped (semantic link set)
				
				//create an AddRegionNodeCommand and execute it
				AddRegionNodeCommand addRegionNodeCommand = new AddRegionNodeCommand(newRegionNode, previousRegionNode);
				addRegionNodeCommand.execute();
				
				//compute a fake ResizeCommand so that all regions have same width
				ChangeBoundsRequest request = new ChangeBoundsRequest();
				request.setType(RequestConstants.REQ_RESIZE);
				request.setResizeDirection(PositionConstants.NORTH);
				int move = newRegionNode.getSize().height-equalSize;
				request.setSizeDelta(new Dimension(0, move));
				ResizeRegionNodeCommand resizeCommand = new ResizeRegionNodeCommand(newRegionNode, request);
				//execute the ResizeCommand
				resizeCommand.execute();
				
				//update previous region node
				previousRegionNode = newRegionNode;
			}

		}
		
		//the ge parent is connected in last becaus an event may be sent at this moment and trigger a refresh.
		if(parent != null)
			parent.getContained().add(graphNode);
		return graphNode;
	}

	/**
	 * 
	 * 
	 * @param parent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#canCreateGraphElement(com.cea.papyrus.diagraminterchange2.di2.GraphElement)
	 */
	@Override
	public boolean canCreateGraphElement(GraphElement parent){
		Debug.debug(this,"Call boolean NodeStateHelper.canCreateGraphElement(GraphElement parent)", channel);

		//this method is called when the State button was clicked on the palette
		//and the mouse cursor is moved across a diagram
		//it says whether (true,false) a State can be created as child of the underlying element
		//the only case here is when hovering over a Region
		if(parent.getSemanticModel() != null){
			Element parentElt= ((Uml1SemanticModelBridge)parent.getSemanticModel()).getElement();
			if(parentElt != null){
				if(parentElt instanceof Region)
					return true;
			}
		}
		return false;
	}

	///////////////////////////////////////////////////////////////////////////
	// UML Management
	///////////////////////////////////////////////////////////////////////////

	/**
	 * 
	 * 
	 * @param umlParent 
	 * @param name 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#createUMLElement(java.lang.String, org.eclipse.uml2.uml.Element)
	 */
	@Override
	public  Element createUMLElement(String name, Element umlParent){
		Debug.debug(this,"Call Element NodeStateHelper.createUMLElement(String name, Element umlParent)", channel); //$NON-NLS-1$

		State aState = UMLFactory.eINSTANCE.createState();
		aState.setName(name);
		((Region)umlParent).getSubvertices().add(aState);
		return aState;
	}

	/**
	 * 
	 * 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#canCreateUMLElement(org.eclipse.uml2.uml.Element)
	 */
	@Override
	public boolean canCreateUMLElement(Element umlParent){
		Debug.debug(this,"Call boolean NodeStateHelper.canCreateUMLElement(Element umlParent)", channel); //$NON-NLS-1$

		if(umlParent instanceof Region)
			return true;
		return false;
	}

	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#addUMLElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	@Override
	public boolean addUMLElement(Element element, Element umlParent){
		Debug.debug(this,"Call Element NodeStateHelper.addUMLElement(String name, Element umlParent)", channel); //$NON-NLS-1$

		if(umlParent instanceof Region)
			return ((Region)umlParent).getSubvertices().add((Vertex)element);
		return false;
	}

	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#removeUMLElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	@Override
	public boolean removeUMLElement(Element element, Element umlParent){
		Debug.debug(this,"Call Element NodeStateHelper.removeUMLElement(String name, Element umlParent)", channel); //$NON-NLS-1$

		if(umlParent instanceof Region)
			return ((Region)umlParent).getSubvertices().remove(element);
		return false;
	}

	/**
	 * 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#getBaseString()
	 */
	@Override
	public String getBaseString(){
		return "State_"; //$NON-NLS-1$
	}

	/**
	 * 
	 * 
	 * @param umlParent 
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#getNewUMLElementName(org.eclipse.uml2.uml.Element)
	 */
	@Override
	public String getNewUMLElementName(Element umlParent){
		String name = ""; //$NON-NLS-1$

		//i <10000: avoid infinite loops
		for(int i=0; i< 10001; i++ ) {
			name = getBaseString()+i;
			if(umlParent == null)
				return name;
			if(((Region)umlParent).getSubvertex(name) == null)
				return name;
		}
		return getBaseString()+"X"; //$NON-NLS-1$
	}

	/**
	 * 
	 * 
	 * @param graphElement 
	 * 
	 * @see com.cea.papyrus.common.helper.NodeHelper#setGraphElementPreferences(com.cea.papyrus.diagraminterchange2.di2.GraphElement)
	 */
	@Override
	public void setGraphElementPreferences(GraphElement graphElement){
		//get the preference store
		IPreferenceStore store = Activator.getDefault().getPreferenceStore();

		//set colors and font
		graphElement.setBackgroundColor(StateMachineDiagramPreferenceConstants.getPreferenceStateBackgroundColor(store));
		graphElement.setBorderColor(StateMachineDiagramPreferenceConstants.getPreferenceStateBorderColor(store));
		graphElement.setFontColor(StateMachineDiagramPreferenceConstants.getPreferenceStateFontColor(store));
		FontData[] fontdata = StateMachineDiagramPreferenceConstants.getPreferenceStateFont(store);
		if (fontdata.length>0){
			graphElement.setFontFamily(fontdata[0].getName());
			graphElement.setFontSize(fontdata[0].getHeight());
		}
	}
}
